﻿#include "FITKGraphObjectPostCut.h"

// VTK
#include <vtkImplicitPlaneWidget2.h>
#include <vtkImplicitCylinderWidget.h>
#include <vtkSphereWidget2.h>
#include <vtkSliderWidget.h>
#include <vtkImplicitPlaneRepresentation.h>
#include <vtkImplicitCylinderRepresentation.h>
#include <vtkSphereRepresentation.h>
#include <vtkSliderRepresentation2D.h>
#include <vtkProperty2D.h>
#include <vtkProperty.h>
#include <vtkVolume.h>
#include <vtkAbstractVolumeMapper.h>
#include <vtkDataSetTriangleFilter.h>
#include <vtkOutlineFilter.h>

// Graph and filter
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor2D.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKMaxMinValueFilter.h"
#include "FITKGraphCommons.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructuralPost/FITKCutViewManager.h"
#include "FITK_Interface/FITKInterfaceStructuralPost/FITKAbstractStruPostVTK.h"

namespace Exchange
{
    // For callback.
    //@{
    void vtkCutterCallback::Execute(vtkObject *caller, unsigned long eventId, void *callData)
    {
        // For extended function.
        Q_UNUSED(caller);
        Q_UNUSED(eventId);
        Q_UNUSED(callData);
    }

    void vtkSliderCallback::setSphereWidget(vtkSphereWidget2* widget)
    {
        m_sphereWidget2 = widget;
    }

    void vtkSliderCallback::Execute(vtkObject *caller, unsigned long eventId, void *callData)
    {
        Q_UNUSED(eventId);
        Q_UNUSED(callData);

        // Get the caller( slider widget ).
        vtkSliderWidget* sliderWidget = vtkSliderWidget::SafeDownCast(caller);
        if (!sliderWidget || !m_sphereWidget2)
        {
            return;
        }

        // Get the representation of the sphere.
        vtkSliderRepresentation2D* sliderRep = vtkSliderRepresentation2D::SafeDownCast(sliderWidget->GetSliderRepresentation());
        vtkSphereRepresentation* sphereRep = vtkSphereRepresentation::SafeDownCast(m_sphereWidget2->GetRepresentation());
        if (!sliderRep || !sphereRep)
        {
            return;
        }

        // Set the sphere radius.
        double value = sliderRep->GetValue();
        sphereRep->SetRadius(value);
    }
    //@}

    FITKGraphObjectPostCut::FITKGraphObjectPostCut(AbaqusData::FITKDataCase* caseData, Interface::FITKAbstractViewCut* cutData)
        : FITKGraphObjectPostModelBase(caseData, cutData)
    {
        if (!cutData)
        {
            return;
        }

        // Initialize base actor.
        initActors();

        // Initialize widgets.
        initWidgets();

        // Set the hue type to rainbow.
        setHueType(HueType::Rainbow);

        // Initialize the min and max labels and glyph actors.
        generateGraph(cutData);

        // Save the post data information.
        m_graphInfo.Type = PostCutGraph;
        m_graphInfo.PostCutId = cutData->getDataObjectID();
        m_graphInfo.PostModelId = cutData->getModelID();
    }

    FITKGraphObjectPostCut::~FITKGraphObjectPostCut()
    {
        // Remove observer.
        if (m_cutWidget)
        {
            m_cutWidget->RemoveAllObservers();
        }

        // Delete the call
        deleteObj(m_cutCallback);
        deleteObj(m_sliderCallback);
    }

    void FITKGraphObjectPostCut::initWidgets()
    {
        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData)
        {
            return;
        }

        // Get the bounds of the cut data.
        double bounds[6]{ 0., 0., 0., 0., 0., 0. };
        cutData->getInputBounds(bounds);

        // Get the center of the input.
        double center[3]{ (bounds[0] + bounds[1]) / 2., 
            (bounds[2] + bounds[3]) / 2, 
            (bounds[4] + bounds[5]) / 2 };

        // Calculate the best radius for the cylinder or sphere.
        double radius = sqrt(pow(bounds[1] - bounds[0], 2) +
            pow(bounds[3] - bounds[2], 2) +
            pow(bounds[5] - bounds[4], 2)) / 5.;

        // Initialize the data center.
        cutData->setOrigin(center[0], center[1], center[2]);

        // The widget representation.
        vtkWidgetRepresentation* rep{ nullptr };

        // Create and initialize the interactive widget by the cutter's shape type.
        switch (cutData->getShapeType())
        {
        case Interface::FITKAbstractViewCut::CutShapeType::Plane:
        {
            // Create default representation and disable draging out line.
            vtkImplicitPlaneWidget2* planeWidget = vtkImplicitPlaneWidget2::New();
            planeWidget->CreateDefaultRepresentation();
            vtkImplicitPlaneRepresentation* planeRep = planeWidget->GetImplicitPlaneRepresentation();
            planeRep->SetOutlineTranslation(false);
            planeRep->SetOutsideBounds(false);
            planeRep->SetTubing(true);
            m_cutWidget = planeWidget;
            rep = planeRep;

            // Set the widget size to the input's bounds.
            planeRep->SetPlaceFactor(1.1);

            // Place the widget.
            planeRep->PlaceWidget(bounds);

            // Update the plane normal and origin.
            Interface::FITKViewCutPlane* planeCut = dynamic_cast<Interface::FITKViewCutPlane*>(cutData);
            if (planeCut)
            {
                double normal[3]{ 0., 0., 0. };
                planeCut->getNormalAxis(normal);
                planeRep->SetNormal(normal);

                planeRep->SetOrigin(center);
            }

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Cylinder:
        {
            // Create default representation and disable draging out line.
            vtkImplicitCylinderWidget* cylinderWidget = vtkImplicitCylinderWidget::New();
            cylinderWidget->CreateDefaultRepresentation();
            vtkImplicitCylinderRepresentation* cylinderRep = cylinderWidget->GetCylinderRepresentation();
            cylinderRep->SetOutlineTranslation(false);
            cylinderRep->SetOutsideBounds(false);
            cylinderRep->SetTubing(true);
            m_cutWidget = cylinderWidget;
            rep = cylinderRep;

            Interface::FITKViewCutCylinder* cylinderCut = dynamic_cast<Interface::FITKViewCutCylinder*>(cutData);
            if (cylinderCut)
            {
                cylinderCut->setRadius(radius);
            }

            // Set the widget size to the input's bounds.
            cylinderRep->SetPlaceFactor(1.1);

            // Place the widget.
            cylinderRep->PlaceWidget(bounds);

            // Initialize the widget center and radius.
            cylinderRep->SetCenter(center);
            cylinderRep->SetRadius(radius);

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Sphere:
        {
            // Create default representation.
            vtkSphereWidget2* sphereWidget = vtkSphereWidget2::New();
            sphereWidget->CreateDefaultRepresentation();
            rep = sphereWidget->GetRepresentation();
            m_cutWidget = sphereWidget;

            // Enable scaling.
            sphereWidget->SetScalingEnabled(true);

            // Initialize the radius of the data.
            Interface::FITKViewCutSphere* sphereCut = dynamic_cast<Interface::FITKViewCutSphere*>(cutData);
            if (sphereCut)
            {
                sphereCut->setRadius(radius);
            }

            // Initialize the widget center and radius.
            vtkSphereRepresentation* sphereRep = vtkSphereRepresentation::SafeDownCast(rep);
            if (sphereRep)
            {
                // Enable the handle.
                sphereRep->SetRadialLine(true);
                sphereRep->SetHandleText(true);
                sphereRep->SetHandleVisibility(true);

                // Set the widget size to the input's bounds.
                sphereRep->SetPlaceFactor(1.1);

                // Place the widget.
                sphereRep->PlaceWidget(bounds);

                sphereRep->SetCenter(center);  
                sphereRep->SetRadius(radius);

                // Transparent white surface and red wireframe sphere.
                sphereRep->SetThetaResolution(15);
                sphereRep->SetPhiResolution(15);
                sphereRep->SetRepresentationToSurface();
                sphereRep->GetSphereProperty()->SetOpacity(0.3);
                sphereRep->GetSphereProperty()->SetEdgeVisibility(true);
                sphereRep->GetSphereProperty()->SetEdgeColor(1., 0., 0.);
                sphereRep->GetSphereProperty()->SetLineWidth(2.);
                sphereRep->GetSphereProperty()->SetRenderLinesAsTubes(true);
            }  

            // Create the slider widget and callback.
            m_sliderWidget = vtkSliderWidget::New();
            // m_sliderWidget->CreateDefaultRepresentation();
            m_sliderWidget->SetAnimationModeToAnimate();
            m_sliderWidget->SetNumberOfAnimationSteps(30);
            m_sliderCallback = new vtkSliderCallback;
            m_sliderCallback->setSphereWidget(sphereWidget);
            m_sliderWidget->AddObserver(vtkCommand::InteractionEvent, m_sliderCallback);
            addWidget(m_sliderWidget);

            // Update the slider range.
            double xDelta = bounds[1] - bounds[0];
            double yDelta = bounds[3] - bounds[2];
            double zDelta = bounds[5] - bounds[4];
            vtkSmartPointer<vtkSliderRepresentation2D> sliderRep = vtkSmartPointer<vtkSliderRepresentation2D>::New();

            // Initialize the slider property.
            sliderRep->SetMinimumValue(0);
            sliderRep->SetMaximumValue(qMax(qMax(xDelta, yDelta), zDelta) * 1.1);
            sliderRep->SetValue(radius);
            sliderRep->SetTitleText("Radius");
            sliderRep->GetSliderProperty()->SetColor(0., 1., 0.);

            // Initialize the slider's size and position.
            sliderRep->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay();
            sliderRep->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay();

            // Top of the viewport.
            sliderRep->GetPoint1Coordinate()->SetValue(0.3, 0.9);
            sliderRep->GetPoint2Coordinate()->SetValue(0.7, 0.9);
            sliderRep->SetSliderLength(0.001);
            sliderRep->SetSliderWidth(0.03);
            sliderRep->SetEndCapLength(0.002);

            m_sliderWidget->SetRepresentation(sliderRep);
            m_sliderWidget->EnabledOff();

            break;
        }
        default:
            return;
        }

        // Initialize the center of the data.
        cutData->setOrigin(center[0], center[1], center[2]);

        // Add the event callback.
        m_cutCallback = new vtkCutterCallback;
        m_cutWidget->AddObserver(vtkCommand::InteractionEvent, m_cutCallback);

        // Disable the widget.
        m_cutWidget->EnabledOff();

        //// Initialize the widget.
        //if (rep)
        //{
        //    // Use bounds.
        //    rep->SetUseBounds(true);

        //    // Disable pickable and draggable.
        //    rep->SetDragable(false);
        //    rep->SetPickable(false);
        //    rep->SetPickingManaged(false);
        //}

        // Add to widget list.
        addWidget(m_cutWidget);
    }

    void FITKGraphObjectPostCut::generateGraph(Interface::FITKAbstractViewCut* cutData)
    {
        Q_UNUSED(cutData);

        // Create labels actors.
        initializeLabelsActors();

        // Initialize the input with the cut data's output.
        //@{
        update();
        //@}
    }

    void FITKGraphObjectPostCut::setWidgetVisible(bool flag)
    {
        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData || !m_cutWidget)
        {
            return;
        }

        bool dataVisible = getDataVisibility();

        // Get the bounds of the cut data.
        double bounds[6]{ 0., 0., 0., 0., 0., 0. };
        cutData->getInputBounds(bounds);

        // Update the widget bounds.
        switch (cutData->getShapeType())
        {
        case Interface::FITKAbstractViewCut::CutShapeType::Plane:
        {
            vtkImplicitPlaneWidget2* planeWidget = vtkImplicitPlaneWidget2::SafeDownCast(m_cutWidget);
            if (!planeWidget)
            {
                return;
            }

            vtkImplicitPlaneRepresentation* planeRep = planeWidget->GetImplicitPlaneRepresentation();
            if (!planeRep)
            {
                return;
            }

            // Update the bounds.
            double normal[3]{ 0., 0., 0. };
            planeRep->GetNormal(normal);
            planeRep->PlaceWidget(bounds);
            planeRep->SetNormal(normal);

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Cylinder:
        {
            vtkImplicitCylinderWidget* cylinderWidget = vtkImplicitCylinderWidget::SafeDownCast(m_cutWidget);
            if (!cylinderWidget)
            {
                return;
            }

            // Set the cylinder direction, origin and redius.
            vtkImplicitCylinderRepresentation* cylinderRep = cylinderWidget->GetCylinderRepresentation();
            if (!cylinderRep)
            {
                return;
            }

            // Update the bounds.
            cylinderRep->PlaceWidget(bounds);

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Sphere:
        {
            // Enable or disable the slider widget.
            if (m_sliderWidget)
            {
                m_sliderWidget->SetEnabled(flag && dataVisible);
            }

            break;
        }
        default:
            return;
        }

        m_cutWidget->SetEnabled(flag && dataVisible);
    }

    void FITKGraphObjectPostCut::setPickable(bool isPickable, int dim)
    {
        Q_UNUSED(dim);

        if (m_fActor)
        {
            m_fActor->SetPickable(isPickable);
        }
    }

    vtkAlgorithmOutput* FITKGraphObjectPostCut::getOutputPort(bool forRendering)
    {
        vtkAlgorithmOutput* outputPort{ nullptr };
        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData)
        {
            return nullptr;
        }

        // Get the output port.
        vtkAlgorithm* outAlg{ nullptr };
        if (forRendering)
        {
            outAlg = cutData->getRenderOutputAlgorithm();
        }
        else
        {
            outAlg = cutData->getOutputAlgorithm();
        }

        if (outAlg)
        {
            outputPort = outAlg->GetOutputPort();
        }
        
        return outputPort;
    }

    vtkDataSet* FITKGraphObjectPostCut::getOutput(bool forRendering)
    {
        vtkDataSet* output{ nullptr };
        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData)
        {
            return nullptr;
        }

        // Get the output port.
        vtkAlgorithm* outAlg{ nullptr };
        if (forRendering)
        {
            outAlg = cutData->getRenderOutputAlgorithm();
        }
        else
        {
            outAlg = cutData->getOutputAlgorithm();
        }

        if (outAlg)
        {
            output = vtkDataSet::SafeDownCast(outAlg->GetOutputDataObject(0));
        }

        return output;
    }

    Interface::FITKMeshFeatureVTK* FITKGraphObjectPostCut::getMeshFeature()
    {
        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData)
        {
            return nullptr;
        }

        return cutData->getMeshFeature();
    }

    void FITKGraphObjectPostCut::update(bool forceUpdate)
    {
        Q_UNUSED(forceUpdate);

        Interface::FITKAbstractViewCut* cutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!cutData || !m_fActor || !m_maxFilter || !m_minFilter)
        {
            return;
        }

        // Update the data object.
        cutData->update();

        // Update the cutter filter.
        updateCutFilter();

        // Change the input filter if neccessary.
#ifdef RENDER_VOLUME_TEST
        vtkAlgorithm* outAlg = cutData->getOutputAlgorithm();
#else
        vtkAlgorithm* outAlg = cutData->getRenderOutputAlgorithm();
#endif
        
        if (outAlg)
        {
            vtkAlgorithmOutput* outputPort = outAlg->GetOutputPort();

            m_maxFilter->SetInputConnection(outputPort);
            m_minFilter->SetInputConnection(outputPort);
            m_fActor->setInputConnection(outputPort);
            
#ifdef RENDER_VOLUME_TEST
            if (m_volFilter)
            {
                m_volFilter->SetInputConnection(outputPort);
            }

            if (m_volOutlineFilter)
            {
                m_volOutlineFilter->SetInputConnection(outputPort);
            }
#endif
        }

        // Update the post functions.
        this->Superclass::update(forceUpdate);

        // Update the visibility.
        updateVisibility();
    }

    bool FITKGraphObjectPostCut::getDataVisibility()
    {
        Interface::FITKAbstractViewCut* abCutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!abCutData)
        {
            return false;
        }

        // Check the view cut setting first.
        int modelId = abCutData->getModelID();

        // Try to get the post model data.
        Interface::FITKAbstractStructuralPostVTK* postData = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKAbstractStructuralPostVTK>(modelId);
        if (!postData)
        {
            return false;
        }

        if (!postData->getShowViewCut())
        {
            return false;
        }

        bool visible = FITKGraphObject3D::getDataVisibility();
        if (!visible)
        {
            return false;
        }

        // Check the setting of visibility.
        return abCutData->isVisible();
    }

    void FITKGraphObjectPostCut::updateCutFilter()
    {
        Interface::FITKAbstractViewCut* abCutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!abCutData || !m_cutWidget)
        {
            return;
        }

        // Get the representation of different type.
        switch (abCutData->getShapeType())
        {
        case Interface::FITKAbstractViewCut::CutShapeType::Plane:
        {
            Interface::FITKViewCutPlane* planeCut = dynamic_cast<Interface::FITKViewCutPlane*>(abCutData);
            vtkImplicitPlaneWidget2* planeWidget = vtkImplicitPlaneWidget2::SafeDownCast(m_cutWidget);
            if (!planeWidget || !planeCut)
            {
                return;
            }

            vtkImplicitPlaneRepresentation* planeRep = planeWidget->GetImplicitPlaneRepresentation();
            if (!planeRep)
            {
                return;
            }

            // Set the plane normal and origin.
            double* normal = planeRep->GetNormal();
            double* origin = planeRep->GetOrigin();

            planeCut->setNormalAxis(normal[0], normal[1], normal[2]);
            planeCut->setOrigin(origin[0], origin[1], origin[2]);

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Cylinder:
        {
            Interface::FITKViewCutCylinder* cylinderCut = dynamic_cast<Interface::FITKViewCutCylinder*>(abCutData);
            vtkImplicitCylinderWidget* cylinderWidget = vtkImplicitCylinderWidget::SafeDownCast(m_cutWidget);
            if (!cylinderWidget || !cylinderCut)
            {
                return;
            }

            // Set the cylinder direction, origin and redius.
            vtkImplicitCylinderRepresentation* cylinderRep = cylinderWidget->GetCylinderRepresentation();
            if (!cylinderRep)
            {
                return;
            }

            double* axis = cylinderRep->GetAxis();
            double* origin = cylinderRep->GetCenter();
            double radius = cylinderRep->GetRadius();

            cylinderCut->setAxis(axis[0], axis[1], axis[2]);
            cylinderCut->setOrigin(origin[0], origin[1], origin[2]);
            cylinderCut->setRadius(radius);

            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Sphere:
        {
            Interface::FITKViewCutSphere* sphereCut = dynamic_cast<Interface::FITKViewCutSphere*>(abCutData);
            vtkSphereWidget2* sphereWidget = vtkSphereWidget2::SafeDownCast(m_cutWidget);
            if (!sphereWidget || !sphereCut)
            {
                return;
            }

            // Set the sphere radius and origin.
            vtkWidgetRepresentation* rep = sphereWidget->GetRepresentation();
            vtkSphereRepresentation* sphereRep = vtkSphereRepresentation::SafeDownCast(rep);
            if (!sphereRep)
            {
                return;
            }

            double* origin = sphereRep->GetCenter();
            double radius = sphereRep->GetRadius();

            sphereCut->setOrigin(origin[0], origin[1], origin[2]);
            sphereCut->setRadius(radius);

            break;
        }
        default:
            return;
        }

        // Update the cutter parameters.
        abCutData->update();
    }

    bool FITKGraphObjectPostCut::getFixedBounds(double* bounds)
    {
        // If the cut actor is invisible, then return false.
        Interface::FITKAbstractViewCut* abCutData = dynamic_cast<Interface::FITKAbstractViewCut*>(_dataObj);
        if (!abCutData || !m_cutWidget || !bounds || !m_fActor || !getDataVisibility())
        {
            return false;
        }

        // The widget representation.
        vtkWidgetRepresentation* rep{ nullptr };

        // Place the widget with the input's bounds.
        switch (abCutData->getShapeType())
        {
        case Interface::FITKAbstractViewCut::CutShapeType::Plane:
        {
            vtkImplicitPlaneWidget2* planeWidget = vtkImplicitPlaneWidget2::SafeDownCast(m_cutWidget);
            if (!planeWidget)
            {
                return false;
            }

            rep = planeWidget->GetImplicitPlaneRepresentation();
            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Cylinder:
        {
            vtkImplicitCylinderWidget* cylinderWidget = vtkImplicitCylinderWidget::SafeDownCast(m_cutWidget);
            if (!cylinderWidget)
            {
                return false;
            }

            rep = cylinderWidget->GetCylinderRepresentation();
            break;
        }
        case Interface::FITKAbstractViewCut::CutShapeType::Sphere:
        {
            vtkSphereWidget2* sphereWidget = vtkSphereWidget2::SafeDownCast(m_cutWidget);
            if (!sphereWidget)
            {
                return false;
            }

            rep = sphereWidget->GetRepresentation();
            break;
        }
        default:
            return false;
        }

        if (!bounds || !rep || !m_fActor)
        {
            return false;
        }

        // Get the result actor bounds.
        double aBds[6]{ 0., 0., 0., 0., 0., 0. };
        getActorBounds(m_fActor, aBds);

        // Get the cut widget bounds.
        double wBds[6]{ 0., 0., 0., 0., 0., 0. };
        getActorBounds(rep, wBds);

        // Merge the bounds.
        bounds[0] = qMin(aBds[0], wBds[0]);
        bounds[1] = qMax(aBds[1], wBds[1]);
        bounds[2] = qMin(aBds[2], wBds[2]);
        bounds[3] = qMax(aBds[3], wBds[3]);
        bounds[4] = qMin(aBds[4], wBds[4]);
        bounds[5] = qMax(aBds[5], wBds[5]);

        return true;
    }
}   // namespace Exchange